import java.util.Arrays;
import java.util.Stack;

/**
 * Created with IntelliJ IDEA.
 * Description:排序
 * User: 牛孟俊
 * Date: 2023-10-21
 * Time: 13:47
 */
public class Sort {
/**
 * 时间复杂度：最坏情况  O（N^2） ---->>  4 3 2 1
 * 最好情况：O（N） 数据有序的情况下 ——>> 1 2 3
 * 空间复杂度：O(1)
 *
 * 一个本身就稳定的排序可以实现不稳定的排序
 * 一个本身不稳定的排序不可以实现稳定的排序
 *
 * 稳定性:稳定的， (array[j] > tmp)
 * 不稳定： (array[j] >= tmp)
 *
 */
    public static void insertSort1(int[] array) {
        for(int i = 1; i < array.length;i++) {
            int tmp = array[i];
            int j = i-1;
            for(;j >= 0;j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                } else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }


    /**
     * 希尔排序
     * 增量(gap):采取n/2的方式来取值
     * 时间复杂度:O(N^1.25 ~ N^1.5) 认为是O(N^1.3)
     * */

    private static void shell(int[] array,int gap) {
        for(int i = gap; i < array.length; i++) {
            int j = i-gap;
            int tmp = array[i];
            for(;j >= 0; j-=gap) {
                if(array[j] > tmp){
                    array[j+gap] = array[j];
                } else {
                    break;
                }
            }
            array[j+gap] = tmp;
        }
    }
    public static void shellSort1(int[] array) {
        int gap = array.length;
        while (gap > 1) {
            gap /=  2;
            shell(array,gap);
        }
    }


    /**
     *选择排序
     * 时间复杂度：O(n^2) 不管本身是否有序 都需要进行变量两次数组 所以都是这个复杂度
     * 空间复杂度: O（1）
     * 稳定性:不稳定的
     * */
    public static void selectSort1(int[] array) {
        for(int i = 0; i < array.length; i++) {
            int minIndex = i;
            for(int j=i+1;j < array.length; j++) {
                if(array[minIndex] > array[j]) {
                    minIndex = j;
                }
            }
            Swap(array,minIndex,i);
        }
    }

    private static void Swap(int[] array,int i,int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
//第二种思路
    public static void selectSort(int[] array) {
        int left = 0;
        int right = array.length - 1;
        //当left = right 说明这个数组已经排好序了
        while(left < right) {
            int maxIndex = left;
            int minIndex = left;
            for(int i = left + 1; i <= right; i++) {
                if(array[i] < array[minIndex]) {
                    minIndex = i;
                }
                if(array[i] > array[maxIndex]) {
                    maxIndex = i;
                }
            }
            Swap(array,minIndex,left);
            //有可能0下标是最大值，最小值交换完成之后，最大值就换到最小值交换前的位置了，所以需要进行判断是否0下标是最大值
            if(maxIndex == left) {
                maxIndex = minIndex;
            }
            Swap(array,maxIndex,right);

            left++;
            right--;
        }
    }


    // 插入排序
    public static void insertSort(int[] array){
        for(int i = 1; i < array.length;i++) {
            int tmp = array[i];//存放i下标的值
            int j = i - 1;
            for (; j >= 0; j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                } else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

    //希尔排序



    /**
     * 堆排序
     * 升序：大根堆
     * 降序：小根堆
     * */
    public static void heapSort(int[] array) {
        createBigHeap(array);
        int end = array.length-1;
        while (end > 0) {
            //大根堆建立完成了，需要进行调整，变成升序的
            Swap(array,end,0);
            shiftDown(array,0,end);
            end--;
        }
    }

    public static void createBigHeap(int[] array) {
        for (int i = (array.length - 1 -1) / 2; i >= 0; i--) {
            shiftDown(array,i,array.length);
        }
    }

    public static void shiftDown(int[] array,int parent,int len) {
        int child = 2 * parent +1;//左孩子
        while(child < len) {
            if(child+1 < len && array[child] < array [child+1]) {
                child++;
            }
            if(array[parent] < array[child]) {
                Swap(array,parent,child);
                parent = child;
                child = 2*parent+1;
            } else {
                break;
            }
        }
    }

    /**
     *冒泡排序
     *时间复杂度：O(n^2)
     *空间复杂度: O(1)
     * 稳定性：稳定
     *
     * 加了优化之后时间复杂度可能是O(n)只进行一次*/
    public static void bubbleSort(int[] array) {
        for (int i =0; i < array.length-1; i++) {
            boolean flg = false;
            for (int j = 0;j < array.length-1 -i; j++) {
                if(array[j] < array[j+1]) {
                    Swap(array,j,j+1);
                    flg = true;
                }
            }
            if(!flg) { 
                break;
            }
        }
    }

    /**
     * 快速排序（类似于二叉树的前序遍历）
     * */

    private static void quick2(int[] array,int start,int end) {
        if(start >= end) return;//此时已经有序，结束递归
        int pivot = parttion(array,start,end);//寻找到parttion（也就是课件中的key）
        quick(array,start,pivot-1);//左树
        quick(array,pivot+1,end);//右树
    }
    private static int parttion2(int[] array,int left,int right) {
        int i = left;//相当于start
        int tmp = array[left];
        while (left < right) {
            //left和right分别是单独的循环
            while (left < right && array[right] >= tmp) {
            //必须要有等号，不然后面交换没有意义（可以参考left下标和right下标的值相等情况）
            right--;//找小于tmp的值
            }
            while (left < right && array[right] <= tmp) {
                //必须要有等号，不然后面交换没有意义（可以参考left下标和right下标的值相等情况）
                left++;//找大于tmp的值
            }
            Swap(array,left,right);
        }
        //left = right的情况
        Swap(array,left,i);
        return left;//返回right也行，因为此时right = left
    }
    public static void quickSort2(int[] array) {
        quick(array,0, array.length - 1);
    }
    //作业：
    // 插入排序
    /*public static void insertSort(int[] array){
        for(int i = 1; i < array.length;i++) {
            int tmp = array[i];//存放i下标的值
            int j = i - 1;
            for (; j >= 0; j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                } else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }
    //作业
    //希尔排序
    public static void shellSort(int[] array){
        //进行分组
        int gap = array.length;
        while(gap > 1) {
            gap/=2;
            shell(array,gap);
        }
    }

    private static void shell(int[] array,int gap) {
        for (int i = gap; i < array.length; i++) {
            int j = i - gap;
            int tmp = array[i];//用来存放I下标的值的
            for (; j >= 0; j -= gap) {
                if (array[j] > tmp) {
                    array[j + gap] = array[j];
                } else {
                    break;
                }
            }
            array[j + gap] = tmp;
        }
    }
    public static void selectSort(int[] array){
//简单选择排序（思路一）
        for(int i = 0; i < array.length;i++) {
            int minIndex = i;//存放最下的值
            for(int j = i+1; j < array.length;j++) {
                if(array[minIndex] > array[j]) {
                    minIndex = j;
                }
            }
            if(minIndex != i) {
                Swap(array,minIndex,i);
            }
        }
    }
    public static void selectSort(int[] array) {
//选择排序思路二，使用两个下标同时走
        int left = 0;
        int right = array.length - 1;
        while(left < right) {
            int minIndex = left;
            int maxIndex = left;
            for(int i = left+1; i <= right; i++) {
                if(array[i] < array[minIndex]) {
                    minIndex = i;
                }
                if(array[i] > array[maxIndex]) {
                    maxIndex = i;
                }
            }
            Swap(array,minIndex,left);
            //可能left下标是最大值的下标，所以需要进行更新
            if(left == maxIndex) {
                maxIndex = minIndex;
            }
            Swap(array,maxIndex,right);
            left++;
            right--;
        }

    }


// 堆排序

    private static void createHeap(int[] array) {
        for(int i = (array.length - 1 - 1 ) /2; i > 0; i++) {
            shiftDown(array,i,array.length);
        }
    }
    private static void shiftDown(int[] array,int parent,int len) {
        int child = 2*parent+1;
        while(child < len) {
            if(child+1 < len && array[child + 1] > array[child]) {
                child = child+1;
            }
            if(array[child] > array[parent]) {
                Swap(array,child,parent);
                parent = child;
                child = 2*parent+1;
            } else {
                break;
            }
        }
    }
    public static void heapSort(int[] array){
        createHeap(array);
        int end = array.length-1;
        while(end > 0) {
            Swap(array,0,end);
            shiftDown(array,0,end);
            end--;//因为我们child < len
        }
    }


    // 冒泡排序
    public static void bubbleSort1(int[] array){
        for (int i = 0; i < array.length -1; i++) {
            //控制趟数
            boolean flg = false;
            for (int j = 0; j < array.length-1-i; j++) {
                //随着趟数的增加，每趟中的比较次数减少
                if(array[j] > array[j+1]) {
                    Swap(array,j+1,j);
                    flg = true;
                }
            }
            if(!flg) {
                break;
            }
        }
    }
    */


//快速排序
    /*
    * 好的情况：O(n*logn)   ------------- 一棵完全二叉树，每次找到的pivot都是中间的值，能够均匀分成两部分
    * 坏的情况:数组是有序的，O(n^2)
    * 空间复杂度：O(logN) ---- 好的情况
    * O(N)不好的情况，N个数据，高度就是N
    * 稳定性,不稳定的排序
    * */

    //Hearo法
    public static void quickSort1(int[] array) {
        quick1(array,0,array.length-1);
    }

    private static void quick1(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int pivot = parttion1(array,start,end);

        quick1(array,start,pivot-1);

        quick1(array,pivot+1,end);

    }
    private static int parttion1(int[] array,int left,int right) {
        int i = left;
        int tmp = array[left];
        while(left < right) {

            while (left < right && array[right] >= tmp) {
                right--;
            }
            while (left < right && array[left] <= tmp ) {
                left++;
            }

            Swap(array,left,right);
        }
        Swap(array,left,i);
        return left;
    }

//挖坑法
    private static int parttion3(int[] array,int left,int right) {
        int tmp = array[left];
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while (left < right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;//此时left和right相遇
        return left;
    }
    private static void quick3(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int pivot = parttion3(array,start,end);

        quick3(array,start,pivot-1);

        quick3(array,pivot+1,end);

    }
    public static void quickSort3(int[] array) {
        quick3(array,0,array.length-1);
    }

    //数据很少的时候采用的插入排序
    private static void insertSort2(int[] array,int left,int right) {
        for (int i = left+1; i <= left; i++) {
            int tmp = array[i];
            int j = i -1;
            for (;j >= 0; j--) {
                if(array[j] > tmp ) {
                    array[j+1] = array[j];
                } else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

    //三数取中法
    private static int threeNum(int[] array,int left,int right) {
        int mid = (left+right)/2;//中间的下标
        while (left < right) {
            if(array[left] < array[right]) {
                if(array[mid] < array[left]) {
                    return left;
                }else if(array[right] < array[mid]) {
                    return right;
                } else {
                    return mid;
                }
            } else {
                if(array[mid] < array[right]) {
                    return right;
                } else if (array[left] < array[mid]) {
                    return left;
                }else {
                    return mid;
                }
            }
        }
        return left;
    }
    private static int parttion(int[] array,int left,int right) {
        int tmp = array[left];
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];
            while (left < right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;//此时left和right相遇
        return left;
    }
    //定义一个变量记录递归的次数
    public static int count = 0;
    private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        count++;
        //少数据时我们采取插入排序

        if(end - start+1 <= 20) {
            insertSort2(array,start,end);
        } else {
            //三数取中
            int mid = threeNum(array,start,end);
            //交换
            Swap(array,start,mid);

            int pivot = parttion(array,start,end);

            quick(array,start,pivot-1);

            quick1(array,pivot+1,end);
        }


    }
    public static void quickSort(int[] array) {
        quick1(array,0,array.length-1);
    }

    //非递归快速排序
    public static void quickSortNonR(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int start = 0;
        int end = array.length-1;
        //只有当栈当中有数据时，我们才去进行循环，进行排序
        stack.push(start);//未进行排序的数组的首下标
        stack.push(end);//尾下标
        while(!stack.empty()) {
            if(end - start +1 <= 20) {
                insertSort2(array,start,end);
            } else {
                //三数取中
                int mid = threeNum(array,start,end);
                //交换
                Swap(array,start,mid);

                int pivot = parttion(array,start,end);//找基准之后就开始放
                //pivot的坐边至少有两个数据,就将左边入栈
                if(pivot > start+1) {
                    stack.push(start);
                    stack.push(pivot-1);
                }
                //pivot的右边至少有两个数据,就将左边入栈
                if(pivot < end -1) {
                    stack.push(pivot+1);
                    stack.push(end);
                }
                //将左右放入之后就分别开始进行排序了
                end = stack.pop();
                start = stack.pop();
            }

        }

    }

    private static void merge(int[] array,int left,int mid,int right) {
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        int[] tmpArr = new int[right - left + 1];//用来合并的下标
        int k = 0;//tmpArr数组的下标
        while (s1 <= e1 && s2 <= e2) {
            if(array[s1] <= array[s2]) {
                tmpArr[k++] = array[s1++];
                /*
                tmpArr[k] = array[s1];
                k++;
                s1++;
                */
            } else {
                tmpArr[k++] = array[s2++];
            }
        }
        //走到这说明其中一块数据已经走完了
        while(s1 <= e1) {
            tmpArr[k++] = array[s1++];
        }

        while(s2 <= e2) {
            tmpArr[k++] = array[s2++];
        }

        //将tmp这个用来合并的数组的内容拷贝到原数组
        for (int i = 0; i < k; i++) {
            //k使用的是后置++
            array[i+left] = tmpArr[i];
            //array[i+left],因为当mid右边的时候是接着原来数组的
        }

    }
    public static void mergeSortFunc(int[] array,int left,int right) {
        if(left >= right) {
            return;//结束条件
        }
        int mid = (left + right) / 2;

        mergeSortFunc(array,left,mid);

        mergeSortFunc(array,mid+1,right);
        //当一棵树的左右孩子都递归完成之后，就开始将左右合并了
        merge(array,left,mid,right);


    }
    /*
    * 时间复杂度：O(n*logN)
    * 空间复杂度:O(N)
    * 稳定性：稳定的
    * 稳定的排序： 冒泡，插入，归并
    * */
    public static void mergeSort(int[] array) {
        mergeSortFunc(array,0,array.length-1);
    }


    //非递归
    public static void mergeSortNonR(int[] array) {
        int gap = 1;//用来控制每次比较元素的个数
        while(gap < array.length) {
            //gap等于了元素个数的时候说明已经玩成了递归版本的最后一步合并
            for(int i = 0; i < array.length; i = i + gap*2) {

                int left = i;
                int mid = i+gap-1;//第一块数据的最后一个元素
                int right =  mid + gap;
                //mid和right可能越界
                 if(mid >= array.length) {
                     mid = array.length-1;
                 }
                if(right >= array.length) {
                right = array.length-1;
                }

            merge(array,left,mid,right);
            }
            //已经完成了一次gap个数据的排序
            gap*=2;
        }

    }

    /*
    * 计数排序
    * 时间复杂度：范围 + 3n  则：O(MAX(范围，n)) 范围越小复杂度越快   记数排序适用于范围小的
    * 空间复杂度：
    * 稳定性：不稳定
    * */

    public static void countArray(int[] array) {
        //遍历：
        int maxVal = array[0];//假定最大值和最小值都是0下标
        int minVal = array[0];
        for (int i = 0; i < array.length; i++) {
            //时间复杂度：O(n)
            if(array[i] > maxVal) {
                maxVal = array[i];
            }
            if(array[i] < minVal) {
                minVal = array[i];
            }
        }
        //遍历完成找到了最大最小值
        //2.确定计数数组的大小
        int range = maxVal - minVal + 1;
        int[] count = new int[range];
        //3.再次遍历array数组开始计数
        for (int i = 0; i < array.length; i++) {
            //时间复杂度：O(n)
            int k = array[i] - minVal;//array数组中对应的下标
            count[k]++;//开始计数
        }
        //4.遍历count数组将count数组中的数据放到array数组当中           时间复杂度：O(范围+n)
        //i是count数组的下标
        int k = 0;//array数组的下标
        for(int i = 0; i < count.length; i++){
            int val = i+minVal;//当前对于array数组中的值
            while(count[i] != 0) {
                array[k] = val;
                k++;
                count[i]--;
            }
        }

    }


    public static void main(String[] args) {
        int[] array = {8,5,2,9,3,5,6,8};
        //insertSort(array);
        countArray(array);
        System.out.println(Arrays.toString(array));
    }


}
